﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Gunningine
{
    namespace Rendersystems
    {
        namespace OpenGL
        {
            using OpenTK;
            using OpenTK.Graphics;
            using OpenTK.Graphics.OpenGL;
            using Gunningine;

            internal class MeshGL : Mesh, IDisposable
            {
                /// <summary>
                /// Animation frame.
                /// </summary>
                internal class FrameGL : Mesh.Frame
                {
                    public int VertexId { get; set; }
                }

                /// <summary>
                /// Animation.
                /// </summary>
                internal class AnimationGL : Mesh.Animation
                {
                    protected override Mesh.Frame AddFrameImpl()
                    {
                        return new MeshGL.FrameGL();
                    }
                }

                public int IndexId { get; set; }

                /// <summary>
                /// Constructor.
                /// </summary>
                /// <param name="model"></param>
                public MeshGL(ModelResource model)
                    : base(model)
                {
                    this.NumTriangles = model.Triangles.Count;

                    // Upload to OpenGL: triangle indices
                    int triangleCountSize = model.Triangles.Count * 3;
                    int triangleDataSize = triangleCountSize * model.NumVertices;
                    Int32[] indexData = new Int32[triangleDataSize];

                    int indexOffset = 0;
                    for (int j = 0; j < model.Triangles.Count; ++j)
                    {
                        ModelResource.Triangle modelTriangle = model.Triangles[j];
                        indexData[indexOffset + 0] = modelTriangle.vertices[0];
                        indexData[indexOffset + 1] = modelTriangle.vertices[1];
                        indexData[indexOffset + 2] = modelTriangle.vertices[2];
                        indexOffset += 3;
                    }

                    this.IndexId = GL.GenBuffer();

                    GL.BindBuffer(BufferTarget.ElementArrayBuffer, this.IndexId);
                    GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(triangleDataSize * sizeof(Int32)), indexData, BufferUsageHint.StaticDraw);
                    GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);

                    // Vertex data
                    foreach (var kvp in model.Animations)
                    {
                        string animName = kvp.Key;
                        ModelResource.Animation modelAnim = kvp.Value;
                        MeshGL.Animation meshAnim = (MeshGL.Animation)AddAnimation(animName);

                        // Vertex data
                        int vertexDataSize = model.NumVertices * 12;
                        float[] vertexData = new float[vertexDataSize];

                        for (int i = 0; i < modelAnim.frames.Count; ++i)
                        {
                            ModelResource.Frame curModelFrame = modelAnim.frames[i];
                            ModelResource.Frame nextModelFrame = (i == modelAnim.frames.Count - 1)
                                ? modelAnim.frames[0]
                                : modelAnim.frames[i + 1];

                            indexOffset = 0;

                            for (int j = 0; j < curModelFrame.vertices.Count; ++j)
                            {
                                ModelResource.Vertex curModelVertex = curModelFrame.vertices[j];
                                ModelResource.Vertex nextModelVertex = nextModelFrame.vertices[j];

                                // Tex coords
                                vertexData[indexOffset + 0] = curModelVertex.u;
                                vertexData[indexOffset + 1] = curModelVertex.v;

                                // Colour
                                vertexData[indexOffset + 2] = curModelVertex.r;
                                vertexData[indexOffset + 3] = curModelVertex.g;
                                vertexData[indexOffset + 4] = curModelVertex.b;
                                vertexData[indexOffset + 5] = curModelVertex.a;

                                // Normal (aka next frame's position)
                                vertexData[indexOffset + 6] = nextModelVertex.p.X;
                                vertexData[indexOffset + 7] = nextModelVertex.p.Y;
                                vertexData[indexOffset + 8] = nextModelVertex.p.Z;

                                // Position
                                vertexData[indexOffset + 9] = curModelVertex.p.X;
                                vertexData[indexOffset + 10] = curModelVertex.p.Y;
                                vertexData[indexOffset + 11] = curModelVertex.p.Z;

                                indexOffset += 12;
                            }

                            MeshGL.FrameGL meshFrame = (MeshGL.FrameGL)meshAnim.AddFrame();
                            meshFrame.VertexId = GL.GenBuffer();

                            GL.BindBuffer(BufferTarget.ArrayBuffer, meshFrame.VertexId);
                            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vertexDataSize * sizeof(float)), vertexData, BufferUsageHint.StaticDraw);
                            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
                        }
                    }
                }

                /// <summary>
                /// Create new animation.
                /// </summary>
                /// <returns></returns>
                protected override Mesh.Animation AddAnimationImpl()
                {
                    return new MeshGL.AnimationGL();
                }

                /// <summary>
                /// Render.
                /// </summary>
                /// <param name="name"></param>
                public override void Render(string animName, int frame)
                {
                    MeshGL.FrameGL meshFrame = (MeshGL.FrameGL)this.Animations[animName].frames[frame];

                    GL.BindBuffer(BufferTarget.ArrayBuffer, meshFrame.VertexId);

                    GL.EnableClientState(ArrayCap.VertexArray);
                    GL.EnableClientState(ArrayCap.NormalArray);
                    GL.EnableClientState(ArrayCap.TextureCoordArray);
                    GL.EnableClientState(ArrayCap.ColorArray);

                    GL.TexCoordPointer(2, TexCoordPointerType.Float, 12 * sizeof(float), (IntPtr)(0 * sizeof(float)));
                    GL.ColorPointer(4, ColorPointerType.Float, 12 * sizeof(float), (IntPtr)(2 * sizeof(float)));
                    GL.NormalPointer(NormalPointerType.Float, 12 * sizeof(float), (IntPtr)(6 * sizeof(float)));
                    GL.VertexPointer(3, VertexPointerType.Float, 12 * sizeof(float), (IntPtr)(9 * sizeof(float)));

                    GL.BindBuffer(BufferTarget.ElementArrayBuffer, this.IndexId);

                    /*
                    GL.DrawRangeElements(PrimitiveType.Triangles,
                                         0,
                                         numIndices - 1,
                                         numIndices,
                                         DrawElementsType.UnsignedInt,
                                         (IntPtr)0);
                    */
                    GL.DrawElements(PrimitiveType.Triangles, this.NumTriangles * 3,
                                    DrawElementsType.UnsignedInt, (IntPtr)0);

                    GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
                    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

                    GL.DisableClientState(ArrayCap.VertexArray);
                    GL.DisableClientState(ArrayCap.NormalArray);
                    GL.DisableClientState(ArrayCap.TextureCoordArray);
                    GL.DisableClientState(ArrayCap.ColorArray);
                }

                /// <summary>
                /// Dispose.
                /// </summary>
                protected override void DisposeImpl()
                {
                    return;
                    GL.DeleteBuffer(this.IndexId);
                    foreach (var anim in this.animations)
                    {
                        foreach (var frame in anim.Value.frames)
                        {
                            FrameGL fgl = (FrameGL)frame;
                            GL.DeleteBuffer(fgl.VertexId);
                        }
                    }
                }
            }
        }
    }
}
